package com.rankeiot.platform;

import cn.hutool.core.lang.JarClassLoader;
import cn.hutool.core.util.ClassLoaderUtil;
import cn.hutool.core.util.RuntimeUtil;
import com.rankeiot.core.exception.BusinessException;
import com.rankeiot.core.inner.ApplicationEnvironmentPreparedEventListener;
import com.rankeiot.core.util.StringUtil;
import com.rankeiot.core.util.TokenUtil;
import com.rankeiot.core.util.castor.BooleanCastor;
import com.rankeiot.platform.inner.PluginInfo;
import com.rankeiot.platform.inner.RestartEvent;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.ServletContextInitializerBeans;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

@Slf4j
@SpringBootApplication
public class PlatformApplication implements Consumer<String[]> {
    protected static boolean pluginMode = false;
    private static boolean platRun = false;
    private static final String LIB_PATH = "lib";
    private static final String PLUGIN_PATH = "plugin";
    private static ConfigurableApplicationContext rootContext;

    //https://mp.weixin.qq.com/s/KKO2ankjU32zwYPGG6AIoA
    public static void run(String[] args) {
        run(null, args);
    }
    public static void run(Class primarySource, String[] args) {
        platRun = true;
        File pluginDir = new File("./plugin/");
        if (pluginDir.exists() && pluginDir.isDirectory()) {
            pluginMode = true;
        }
        URLClassLoader pluginLoader = initPluginLoader();
        Thread.currentThread().setContextClassLoader(pluginLoader);

        Class mainClass = findMainClass(primarySource);
        //启动应用
        ConfigurableApplicationContext run = SpringApplication.run(mainClass, args);


        Long parentPid = ApplicationEnvironmentPreparedEventListener.parentPid;
        if (parentPid != null) {
            ProcessHandle oldProcess = ProcessHandle.of(parentPid).orElse(null);
            if (oldProcess != null) {
                ApplicationEnvironmentPreparedEventListener.parentPid = null;
                oldProcess.destroy();
                oldProcess.onExit().join();
            }
            Integer port = ApplicationEnvironmentPreparedEventListener.relPort;
            ServletWebServerFactory webServerFactory = getWebServerFactory(run);
            ((TomcatServletWebServerFactory) webServerFactory).setPort(port);
            WebServer webServer = webServerFactory.getWebServer(invokeSelfInitialize(((ServletWebServerApplicationContext) run)));
            webServer.start();
            ((ServletWebServerApplicationContext) run).getWebServer().stop();
            System.setProperty("server.port", port + "");
            try {
                Field field = ServletWebServerApplicationContext.class
                        .getDeclaredField("webServer");
                field.setAccessible(true);
                field.set(run, webServer);
            } catch (NoSuchFieldException e) {
                log.error("启动错误", e);
            } catch (IllegalAccessException e) {
                log.error("启动错误", e);
            }
        }
    }

    private static ServletContextInitializer invokeSelfInitialize(ServletWebServerApplicationContext context) {
        try {
            Method method = ServletWebServerApplicationContext.class.getDeclaredMethod("getSelfInitializer");
            method.setAccessible(true);
            return (ServletContextInitializer) method.invoke(context);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    protected static Collection<ServletContextInitializer> getServletContextInitializerBeans(ConfigurableApplicationContext context) {
        return new ServletContextInitializerBeans(context.getBeanFactory());
    }

    private static ServletWebServerFactory getWebServerFactory(ConfigurableApplicationContext context) {
        String[] beanNames = context.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);

        return context.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
    }

    //查找启动主类
    private static Class findMainClass(Class primarySource) {
        if (primarySource != null) {
            return primarySource;
        }
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        for (StackTraceElement element : stackTraceElements) {
            try {
                Class<?> clazz = Class.forName(element.getClassName());
                if (clazz != PlatformApplication.class && clazz.isAnnotationPresent(SpringBootApplication.class)) {
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return PlatformApplication.class;
    }

    public static void restart() {
        ProcessHandle processHandle = ProcessHandle.current();
        //当前进程ID为1，表示它在容器里面，直接结束，容器会自动重启它
        if (processHandle.pid() == 1l) {
            //RuntimeUtil.exec("kill", "1");
            System.exit(1);
            return;
        }
        if (platRun) {
            restart0();
        } else {
            String dockerId = getDockerId();
            if (dockerId != null) {
                System.out.println("通过Docker重启");
                RuntimeUtil.exec("/usr/bin/docker", "restart", dockerId);
            } else {
                //通过启动脚本重启
                restartByScript();
            }
        }
    }

    //处理脚本路径中的空白
    private static String space(String str) {
        return str.replaceAll("\\s+", "\\\\ ") + " ";
    }

    //处理脚本路径中的空白
    private static String spaceWin(String str) {
        return str.replaceAll("\\s+", "^ ") + " ";
    }

    /**
     * 通过脚本重启
     */
    @SneakyThrows
    private static void restartByScript() {
        String osName = System.getProperty("os.name").toLowerCase();
        String scriptPath = "run.sh";
        boolean isWin = false;
        if (osName.contains("win")) {
            //执行 start.bat restart
            scriptPath = "run.bat";
            isWin = true;
        }
        File dir = new File("").getAbsoluteFile();
        File scriptFile = new File(dir, scriptPath);
        //如果有run脚本
        if (scriptFile.exists()) {
            try {
                ProcessBuilder processBuilder = new ProcessBuilder(scriptFile.getAbsolutePath(), "restart");
                processBuilder.directory(dir);
                processBuilder.start();
                System.out.println("重启成功,退出进程");
                System.exit(0);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            ProcessHandle ph = ProcessHandle.current();
            String javaHome = System.getProperty("java.home");

            String cmd = ph.info().commandLine().orElse(null);
            long pid = ph.pid();

            String runCmd = "";
            if (isWin) {
                StringBuilder sb = new StringBuilder();
                sb.append(spaceWin(javaHome + "\\bin\\javaw.exe"));
                String[] args = ph.info().arguments().orElseThrow();
                for (String arg : args) {
                    sb.append(spaceWin(arg));
                }
                runCmd = """
                        @echo off
                        taskkill /f /pid $pid
                        start $cmd
                        """.replace("$pid", pid + "").replace("$cmd", cmd);
            } else {
                StringBuilder sb = new StringBuilder();
                sb.append(space(javaHome + "/bin/java"));
                String[] args = ph.info().arguments().orElseThrow();
                for (String arg : args) {
                    sb.append(space(arg));
                }
                runCmd = """
                        PID="$pid"
                        if [[ -d /proc/$PID ]];then
                        kill -TERM $PID
                        fi
                        while [[ -d /proc/$PID ]]
                        do
                            sleep 1
                        done
                        nohup $cmd > /dev/null 2>&1 &
                        echo "Start success..."
                        rm $0
                        """.replace("$pid", pid + "").replace("$cmd", sb.toString());
            }
            File tempScriptFile = new File(dir, "tmp_" + scriptPath);
            StringUtil.write(tempScriptFile, runCmd);
            Set<PosixFilePermission> permissions = new HashSet<>();
            permissions.add(PosixFilePermission.OWNER_WRITE);
            permissions.add(PosixFilePermission.OWNER_READ);
            permissions.add(PosixFilePermission.OWNER_EXECUTE);
            Files.setPosixFilePermissions(tempScriptFile.toPath(), new HashSet<>(permissions));
            try {
                ProcessBuilder processBuilder = new ProcessBuilder(tempScriptFile.getAbsolutePath());
                processBuilder.directory(dir);
                processBuilder.start();
                System.out.println("重启成功,退出进程中");
                System.exit(0);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //获取docker id
    public static String getDockerId() {
//        File dockerCli = new File("/usr/bin/docker");
//        if (!dockerCli.exists()) {
//            return null;
//        }
        //docker容器内进程ID总是1
        if(ProcessHandle.current().pid()>1l){
            return null;
        }
        List<String> lines = RuntimeUtil.execForLines("cat", "/proc/1/cgroup");
        for (String line : lines) {
            int idx = line.indexOf(":/docker/");
            if (idx > 0) {
                if (line.length() > idx + 22) {
                    return line.substring(idx + 9, idx + 21);
                }
            }
        }
        return null;
    }

    /**
     * 重启本身
     */
    private static void restart0() {
        ProcessHandle processHandle = ProcessHandle.current();
        //记录本身pid
        StringUtil.write(new File(".restart"), "" + processHandle.pid());
//        String javaHome = System.getProperty("java.home");
//        String osName = System.getProperty("os.name").toLowerCase();
//        String javaBin = javaHome;
//        if (osName.contains("win")) {
//            javaBin = javaHome + "\\bin\\javaw.exe";
//        } else {
//            javaBin = javaHome + "/bin/java";
//        }
        String javaBin=processHandle.info().command().get();
        //只需要重新启动进程即可，新进程启动后会自动结束旧的进程
        String[] args = processHandle.info().arguments().orElse(new String[0]);
        String[] newArgs = new String[args.length + 1];
        System.arraycopy(args, 0, newArgs, 1, args.length);
        newArgs[0] = javaBin;

        File dir = new File("").getAbsoluteFile();

        ProcessBuilder processBuilder = new ProcessBuilder(newArgs);
        processBuilder.directory(dir);
        processBuilder.inheritIO();
        try {
            Process process = processBuilder.start();

            process.onExit().join();
            throw new BusinessException("重启失败");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
//    public void start(String[] args) {
//        pluginMode = true;
//        try {
//            while (true) {
//                URLClassLoader pluginLoader = initPluginLoader();
//                Thread.currentThread().setContextClassLoader(pluginLoader);
//                run(args);
//                synchronized (PlatformApplication.class) {
//                    PlatformApplication.class.wait();
//                }
//            }
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//    }

//    public static void close() {
//        if (rootContext != null)
//            rootContext.close();
//        rootContext = null;
//    }

//    //内部重启
//    public static void innerRestart() {
//        new Thread(() -> {
//            try {
//                Thread.sleep(300);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
//            PlatformApplication.close();
//            Class tc = PlatformApplication.class;
//            synchronized (tc) {
//                tc.notify();
//            }
//        }).start();
//    }

    private static URLClassLoader initPluginLoader() {

        //扫描插件目录
        JarClassLoader pluginLoader = new JarClassLoader(new URL[0]);

        File libDir = new File(LIB_PATH);
        if (libDir.exists() && libDir.isDirectory()) {
            pluginLoader.addJar(libDir);
        }

        File pluginDir = new File(PLUGIN_PATH);
        if (pluginDir.exists() && pluginDir.isDirectory()) {
            for (File dir : pluginDir.listFiles()) {
                if (dir.isDirectory()) {
                    PluginInfo pluginInfo = new PluginInfo();
                    pluginInfo.setPath(dir.getPath());
                    try {
                        pluginInfo.read();
                        Object disabled = pluginInfo.get("disabled");
                        if (BooleanCastor.instance.cast(disabled)) {
                            continue;
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        continue;
                    }
                    pluginLoader.addJar(dir);
                }
            }
        }
        return pluginLoader;
    }

    @Override
    public void accept(String[] args) {
        pluginMode = true;
        run(args);
    }

//    private static void run(String[] args) {
//        SpringApplication application = new SpringApplication(PlatformApplication.class);
//        if (application.getMainApplicationClass() == null) {
//            application.setMainApplicationClass(PlatformApplication.class);
//        }
//        rootContext = application.run(args);
//    }
}
